import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.PrintWriter;

import java.util.ArrayList;
import java.util.Scanner;
import java.util.Vector;

import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableModelEvent;
import javax.swing.table.DefaultTableModel;

/**
 * The data model for the table and controls
 * @author jcoston
 */
public class CSVModel extends DefaultTableModel {
	private static final long serialVersionUID = 1L;
	private ArrayList<ListSelectionListener> listeners = null;
	private boolean adjusting = false;
	private boolean changed = false;
	int[] selection = null;

	/**
	 * Create a new CSVModel with an empty listener list.
	 */
	public CSVModel() {
		super();
		listeners = new ArrayList<ListSelectionListener>();
		setChanged(false);
		setAdjusting(false);
	}

	/**
	 * Add a listener to the listener list
	 * @param l The listener to add
	 */
	public void addListSelectionListener(ListSelectionListener l) {
		listeners.add(l);
	}
	
	/**
	 * Remove the specified listener from the listener list
	 * @param l The listener to remove
	 */
	public void removeListSelectionListener(ListSelectionListener l) {
		listeners.remove(l);
	}
	
	/**
	 * Tell the listeners that the values have changed.
	 * @param e The event
	 */
	public void fireValueChanged(ListSelectionEvent e) {
		for (ListSelectionListener l: listeners) {
			l.valueChanged(e);
		}
	}
	
	/**
	 * Returns true if the table value is adjusting, false otherwise.
	 * @return true if the table value is adjusting, false otherwise.
	 */
	public boolean isAdjusting() {
		return adjusting;
	}

	/**
	 * Set the adjusting value for the model.
	 * @param adjusting the adjusting value to set.
	 */
	public void setAdjusting(boolean a) {
		adjusting = a;
	}

	/**
	 * Returns true if the table has been modified.
	 * @return true if modified, false otherwise
	 */
	public boolean isChanged() {
		return changed;
	}

	/**
	 * Set the value of the changed boolean
	 * @param c The new value of the changed boolean
	 */
	public void setChanged(boolean c) {
		changed = c;
	}

	/**
	 * Gets the currently selected column/row
	 * @return the selection
	 */
	public int[] getSelection() {
		return selection;
	}

	/**
	 * Sets the selection of the table.
	 * @param selection the selection to set
	 */
	public void setSelection(int[] s) {
		selection = s;
		fireValueChanged(new ListSelectionEvent(this, 0, 0, false));
	}

	/**
	 * Returns true if the value is selected, false otherwise.
	 * @param i the value
	 * @return true if selected false otherwise.
	 */
	public boolean isSelected(int i) {
		if (selection == null) {
			return false;
		}
		for (int j = 0; j < selection.length; j++) {
			if (i == selection[j]) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Clears all the data, including the column names, from the model.
	 */
	public void clear() {
		setRowCount(0);
		setColumnCount(0);
		setChanged(false);
		fireTableChanged(new TableModelEvent(this));
	}

	/**
	 * Create a table of c columns and r rows.
	 * @param c the number of columns
	 * @param r the number of rows
	 */
	public void create(int c, int r) {
		clear();
		setAdjusting(true);
		setColumnCount(c);
		setRowCount(r);
		for (int i = 0; i < r; i++) {
			for (int j = 0; j < c; j++) {
				setValueAt("0", i, j);
			}
		}
		setAdjusting(false);
		setChanged(true);
		fireTableChanged(new TableModelEvent(this));
	}
	
	/**
	 * Make all cells uneditable.
	 */
	public boolean isCellEditable(int row, int column){  
	    return false;  
	}

	/**
	 * Read a file and place data in the model.
	 * @param f The file to read from
	 */
	public boolean read(File f) {
		String line;
		BufferedReader inputStream = null;
		ArrayList<String> tokenList = new ArrayList<String>();
		int columns = 0;
		if (f == null) {
			return false;
		}
		try {
			inputStream = new BufferedReader(new FileReader(f));
		} catch (FileNotFoundException e) {
			return false;
		}
		try {
			setAdjusting(true);
			clear();
			if ((line = inputStream.readLine()) != null) {
				Scanner scanner = new Scanner(line);
				scanner.useDelimiter(",");
				while (scanner.hasNext()) {
					tokenList.add(scanner.next());
				}
				columns = tokenList.size();
				setColumnCount(columns);
				setColumnIdentifiers(tokenList.toArray());
				scanner.close();
			}
			while ((line = inputStream.readLine()) != null) {
				Scanner scanner = new Scanner(line);
				scanner.useDelimiter(",");
				tokenList.clear();
				while (scanner.hasNext()) {
					tokenList.add(scanner.next());
				}
				scanner.close();
				if (columns != tokenList.size()) {
					return false;
				}
				addRow(tokenList.toArray());
			}
			inputStream.close();
			setAdjusting(false);
			setChanged(false);
		}
		catch (Exception e) {			
		}
		fireTableChanged(new TableModelEvent(this));
		return true;
	}
	
	/**
	 * Read a file and place data in the model.
	 * @param f The file to read from
	 */
	public boolean populate(String[] headers, String[][] rows) {		
		try {
			setAdjusting(true);
			clear();
			setColumnCount(headers.length);
			addRow(headers);
			for(String[] row : rows) addRow(row);
			setAdjusting(false);
			setChanged(false);
		}
		catch (Exception e) {			
		}
		fireTableChanged(new TableModelEvent(this));
		return true;
	}

	/**
	 * Write the data table to a file.
	 * @param f The file.
	 */
	public boolean write(File f) {
		if (f == null) {
			return false;
		}
		PrintWriter outputStream = null;
		try {
			outputStream = new PrintWriter(new FileWriter(f));
		} catch (Exception e) {
			return false;
		}
		try {
			for (int i = 0; i < getColumnCount(); i++) {
				if (i > 0) {
					outputStream.print(", ");
				}
				outputStream.print(getColumnName(i));
			}
			outputStream.println("");
			for (int i = 0; i < getRowCount(); i++) {
				for (int j = 0; j < getColumnCount(); j++) {
					if (j > 0) {
						outputStream.print(", ");
					}
					outputStream.print(getValueAt(i, j));
				}
				outputStream.println("");			
			}
			outputStream.close();
			setChanged(false);
		}
		catch (Exception e) {	
			return false;
		}
		return true;
	}

	/**
	 * Get the column Names
	 * @return The column names.
	 */
	public String[] getColumnNames() {
		String[] names = new String[getColumnCount()];
		for (int i = 0; i < getColumnCount(); i++) {
			names[i] = getColumnName(i);
		}
		return names;
	}

	/**
	 * Remove the selected columns
	 * @param selection the columns to remove
	 */
	public void removeColumns(int[] selection) {
		if (selection == null || selection.length == 0) {
			return;
		}
		Vector<Vector> data = getDataVector();
		Vector<String> names = new Vector<String>();
		for (int i = 0; i < getColumnCount(); i++) {
			names.add(getColumnName(i));
		}
		for (int i = selection.length - 1; i >= 0; i--) {
			for (int r = 0; r < data.size(); r++) {
				data.get(r).remove(selection[i]);
			}
			names.remove(selection[i]);
		}
		setDataVector(data, names);
	}

	/**
	 * Set the selected column's name
	 * @param n The new name	
	 * @param selection the column name to change.
	 */
	public void setColumnNames(String n, int[] selection) {
		if (selection == null || selection.length == 0) {
			return;
		}
		String[] names = getColumnNames();
		for (int i = 0; i < selection.length; i++) {
			names[selection[i]] = n;
		}
		setColumnIdentifiers(names);		
	}
}
